//
// PomDAVRepo
//
// Copyright (C) jextra.net.
//
//  This file is part of the net.jextra.dep Library.
//
//  The net.jextra.dep Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The net.jextra.dep Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the net.jextra.dep Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.dep.pom;

import java.io.*;
import java.net.*;
import java.util.*;
import net.jextra.dep.*;
import net.jextra.dep.repo.*;
import net.jextra.dep.repo.DepRepoFile.Type;
import net.jextra.dep.repo.version.*;
import org.apache.commons.httpclient.*;
import org.apache.webdav.lib.*;

/**
 * Web service, XML based Pom HTTP Repo.
 */
public class PomDAVRepo implements DepRepo
{
    // ============================================================
    // Fields
    // ============================================================

    public static final int CONNECTION_TIMEOUT = 10000;

    private String name;
    private URL rootURL;
    private int timeout = CONNECTION_TIMEOUT;
    private String username;
    private String password;
    private VersionOperator versionOperator;

    // ============================================================
    // Constructors
    // ============================================================

    public PomDAVRepo( URL rootURL )
    {
        this.rootURL = rootURL;
        versionOperator = new DotVersionOperator();

        // Tell log4j to shutup.
        //        ConsoleAppender app = new ConsoleAppender( new PatternLayout( "%p %t %C:%M %m%n" ) );
        //        app.setTarget( ConsoleAppender.SYSTEM_ERR );
        //        BasicConfigurator.configure( app );
        //        Logger.getRootLogger().setLevel( Level.DEBUG );
    }

    /**
     * Used only by a DepConfReader.
     */
    protected PomDAVRepo()
    {
        versionOperator = new DotVersionOperator();
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    @Override
    public void init( DepRepoParam params )
    {
        try
        {
            name = params.getAttribute( "name" );
            rootURL = new URL( params.getAttribute( "url" ) );
            if ( name == null )
            {
                name = rootURL.toString();
            }

            String timeoutString = params.getAttribute( "timeout" );
            if ( timeoutString != null )
            {
                timeout = Integer.parseInt( timeoutString );
            }

            username = params.getAttribute( "username" );
            password = params.getAttribute( "password" );

            String versionOpString = params.getAttribute( "versionOperator" );
            if ( versionOpString != null )
            {
                versionOperator = (VersionOperator) ( Class.forName( versionOpString ).newInstance() );
            }
        }
        catch ( Exception ex )
        {
            throw new DepException( ex );
        }
    }

    @Override
    public String getName()
    {
        return name;
    }

    @Override
    public int getTimeout()
    {
        return timeout;
    }

    @Override
    public List<DepRepoVersion> getVersions( String componentName )
        throws Exception
    {
        HashMap<String, DepRepoVersion> versions = new HashMap<String, DepRepoVersion>();

        HttpURL url = new HttpURL( new URL( rootURL, componentName + "/" ).toString() );
        url.setUserinfo( username, password );
        try
        {
            WebdavResource res = new WebdavResource( url );
            for ( WebdavResource item : res.listWebdavResources() )
            {
                if ( !item.isCollection() )
                {
                    continue;
                }

                DepRepoVersion version = new DepRepoVersion( this, componentName, item.getName() );
                versions.put( item.getName(), version );
            }
            res.close();
        }
        catch ( HttpException ex )
        {
            return new ArrayList<DepRepoVersion>();
        }

        ArrayList<String> keys = new ArrayList<String>( versions.keySet() );
        Collections.sort( keys, Collections.reverseOrder( versionOperator ) );

        ArrayList<DepRepoVersion> list = new ArrayList<DepRepoVersion>();
        for ( String key : keys )
        {
            list.add( versions.get( key ) );
        }

        return list;
    }

    @Override
    public String getLatestVersionNumber( String componentName )
        throws Exception
    {
        return calcLatestVersionNumber( getVersions( componentName ) );
    }

    @Override
    public String getNextVersionNumber( String componentName )
        throws Exception
    {
        return calcNextVersionNumber( getLatestVersionNumber( componentName ) );
    }

    @Override
    public List<DepRepoFile> getFileEntries( String componentName, String versionNumber, String path )
        throws Exception
    {
        if ( !path.endsWith( "/" ) )
        {
            path += "/";
        }

        ArrayList<DepRepoFile> list = new ArrayList<DepRepoFile>();

        HttpURL url = new HttpURL(
            new URL( rootURL, mergePath( mergePath( componentName, versionNumber ), path ) ).toString() );
        url.setUserinfo( username, password );
        WebdavResource res = new WebdavResource( url );
        for ( WebdavResource item : res.listWebdavResources() )
        {
            DepRepoFile entry = new DepRepoFile( this, componentName, versionNumber, path + item.getName(),
                Type.directory, 0L, item.getGetContentLength() );
            try
            {
                WebdavResource childRes = new WebdavResource( item.getHttpURL() + "/" );
                childRes.close();
            }
            catch ( HttpException ex )
            {
                // Must be a file.
                entry.setType( Type.file );
            }

            list.add( entry );
        }
        res.close();

        return list;
    }

    @Override
    public DepRepoFile getFileEntry( String componentName, String versionNumber, String path )
        throws Exception
    {
        if ( path.endsWith( "/" ) )
        {
            path = path.substring( 0, path.length() - 1 );
        }

        HttpURL url = new HttpURL(
            new URL( rootURL, mergePath( mergePath( componentName, versionNumber ), path ) ).toString() );
        url.setUserinfo( username, password );

        DepRepoFile entry = null;
        WebdavResource res = null;
        try
        {
            res = new WebdavResource( url + "/" );

            new DepRepoFile( this, componentName, versionNumber, path, Type.directory, 0L, res.getGetContentLength() );
        }
        catch ( HttpException ex )
        {
            // Must be a file.
            res = new WebdavResource( url );

            new DepRepoFile( this, componentName, versionNumber, path, Type.file, 0L, res.getGetContentLength() );
        }
        finally
        {
            if ( res != null )
            {
                res.close();
            }
        }

        return entry;
    }

    @Override
    public InputStream getFileEntryInputStream( String componentName, String versionNumber, String path )
        throws IOException
    {
        HttpURL url = new HttpURL(
            new URL( rootURL, mergePath( mergePath( componentName, versionNumber ), path ) ).toString() );
        url.setUserinfo( username, password );

        WebdavResource res = new WebdavResource( url );
        InputStream in = res.getMethodData();
        res.close();

        return in;
    }

    @Override
    public String getComponentDisplayName( String componentName, String versionNumber )
        throws IOException
    {
        return new URL( rootURL, mergePath( componentName, versionNumber ) ).toString();
    }

    @Override
    public void uploadFile( String componentName, String versionNumber, String path, File localFile )
        throws Exception
    {
        if ( path.endsWith( "/" ) )
        {
            path = path.substring( 0, path.length() - 1 );
        }

        //
        // Make directory
        //
        makeDir( new URL( rootURL, mergePath( componentName, versionNumber ) ) + "/" );

        //
        // Do actual upload
        //
        HttpURL url = new HttpURL( rootURL.toString() );
        url.setUserinfo( username, password );

        WebdavResource res = new WebdavResource( url );
        res.putMethod( new URL( rootURL, mergePath( mergePath( componentName, versionNumber ), path ) ).toString(),
            localFile );
        res.close();
    }

    // ----------
    // private
    // ----------

    private void makeDir( String path )
        throws IOException
    {
        if ( path.endsWith( "/" ) )
        {
            path = path.substring( 0, path.length() - 1 );
        }

        HttpURL url = new HttpURL( path + "/" );
        url.setUserinfo( username, password );
        try
        {
            WebdavResource res = new WebdavResource( url );
            res.close();
        }
        catch ( HttpException ex )
        {
            int slash = path.lastIndexOf( "/" );
            if ( slash >= 0 )
            {
                makeDir( path.substring( 0, slash ) );
            }
            WebdavResource res = new WebdavResource( new HttpURL( rootURL.toString() ) );
            res.mkcolMethod( path );
            res.close();
        }
    }

    private String mergePath( String prefix, String suffix )
    {
        String string = prefix.replace( '\\', '/' );

        if ( !string.endsWith( "/" ) )
        {
            string += "/";
        }

        string += suffix;

        return string;
    }

    private String calcLatestVersionNumber( List<DepRepoVersion> versions )
    {
        return versions == null || versions.isEmpty() ? null : versions.get( 0 ).getVersionNumber();
    }

    private String calcNextVersionNumber( String latest )
    {
        return latest == null ? versionOperator.getStartingVersionString() : versionOperator
            .getNextVersionNumber( latest );
    }
}
